﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using ExcelDataReader;
using ShellProgressBar;

namespace mec
{
    class Program
    {
        private static Random random = new Random();
        public static string RandomString(int length)
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            return new string(Enumerable.Repeat(chars, length)
                .Select(s => s[random.Next(s.Length)]).ToArray());
        }
        static ReaderWriterLock locker = new ReaderWriterLock();
        static StreamWriter swKey;
        public static void WriteKey(string text)
        {
            try
            {
                locker.AcquireWriterLock(int.MaxValue);
                swKey.WriteLine(text);
            }
            finally
            {
                locker.ReleaseWriterLock();
            }
        }

        static void Main(string[] args)
        {
            Console.WriteLine("** welcome to j file encoder. **");

            if (args.Length == 0)
            {
                Console.WriteLine($"请输入需要转换的目录名");
                Console.ReadKey();
                return;
            }

            var swLog = new StreamWriter("out");
            var srcDirectory = args[0];
            var files = Directory.GetFiles(srcDirectory);
            swLog.WriteLine(string.Join(",", files));
            swLog.Flush();

            Directory.CreateDirectory(srcDirectory + "/new");
            swKey = new StreamWriter(srcDirectory + "/new/key.txt");

            int totalTicks = files.Length;
            var options = new ProgressBarOptions
            {
                ProgressCharacter = '─',
                ProgressBarOnBottom = true
            };

            var pbar = new ProgressBar(totalTicks, "Initial message", options);
            var mtid = Thread.CurrentThread.ManagedThreadId;
            // System.Console.WriteLine($"tid:{mtid},main");
            swLog.WriteLine($"tid:{mtid},main");

            var failedFiles = new List<string>();
            int finished = 0;
            var tasks = files.Select(srcPath => new Func<Task>(() => Task.Factory.StartNew(() =>
            {
                var key = RandomString(128);
                WriteKey($"{key}, {srcPath}");
                var dstPath = new Regex(@"(.+)[/\\](.+)").Replace(srcPath, @"$1/new/$2");
                var tid = Thread.CurrentThread.ManagedThreadId;
                var cbar = pbar.Spawn(100, "child actions", options);
                var rand = new Random();
                swLog.WriteLine($"tid:{tid},{srcPath} -> {dstPath}");
                var ok = EncodeFile(srcPath, dstPath, key, (per) =>
                {
                    int now = (int) (per * 100);
                    cbar.Tick(now, tid + ":" + srcPath);
                });
                if (!ok)
                {
                    swLog.WriteLine($"fail:${srcPath} ");
                    failedFiles.Add(srcPath);
                }
                else
                {
                    swLog.WriteLine($"succ:${srcPath} ");
                }
                swLog.Flush();

                finished++;
                pbar.Tick($"{finished}/{files.Length}, ${srcPath}");
            })));

            InvokeAsync(tasks, maxDegreeOfParallelism : 8).Wait();
            pbar.Dispose();
            swLog.Close();
            swKey.Close();
            System.Console.WriteLine("run task finish...");
            Console.ReadKey();
        }

        public static async Task InvokeAsync(IEnumerable<Func<Task>> taskFactories, int maxDegreeOfParallelism)
        {
            if (taskFactories == null) throw new ArgumentNullException(nameof(taskFactories));
            if (maxDegreeOfParallelism <= 0) throw new ArgumentException(nameof(maxDegreeOfParallelism));

            // Defensive copy. Similar to what Task.WhenAll/WhenAny does.
            Func<Task>[] queue = taskFactories.ToArray();

            if (queue.Length == 0)
            {
                return;
            }

            List<Task> tasksInFlight = new List<Task>(maxDegreeOfParallelism);
            int index = 0;

            do
            {
                while (tasksInFlight.Count < maxDegreeOfParallelism && index < queue.Length)
                {
                    Func<Task> taskFactory = queue[index++];
                    var t = taskFactory();
                    tasksInFlight.Add(t);
                }

                Task completedTask = await Task.WhenAny(tasksInFlight).ConfigureAwait(false);

                await completedTask.ConfigureAwait(false); // Check result.

                tasksInFlight.Remove(completedTask);
            }
            while (index < queue.Length || tasksInFlight.Count != 0);
        }

        static bool EncodeFile(string input, string output, string key, Action<float> onProgress)
        {
            FileStream ifs = null, ofs = null;
            try
            {
                Directory.CreateDirectory(Path.GetDirectoryName(output));
                ifs = new FileStream(input, FileMode.Open, FileAccess.Read);
                ofs = new FileStream(output, FileMode.Create, FileAccess.Write);
                var buffer = new byte[64 * 1024];
                int count; // actual number of bytes read
                long sum = 0; // total number of bytes read
                long offset = 0;
                long totalLength = ifs.Length;
                byte[] kb = Encoding.ASCII.GetBytes(key);

                while ((count = ifs.Read(buffer, 0, buffer.Length)) > 0)
                {
                    for (int i = 0; i < count; i++, offset++)
                        buffer[i] = (byte) (buffer[i] ^ kb[offset % kb.Length]);

                    ofs.Write(buffer, 0, count);
                    sum += count;
                    onProgress(1.0f * sum / totalLength);
                }
                return true;
            }
            catch
            {
                return false;
            }
            finally
            {
                onProgress(1.0f);
                ifs?.Close();
                ofs?.Close();
            }

        }

        static List < (string path, string key) > ReadInputFromExcel(string path)
        {
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);

            try
            {
                var result = new List < (string path, string key) > ();
                using(var stream = File.Open(path, FileMode.Open, FileAccess.Read))
                {
                    using(var reader = ExcelReaderFactory.CreateReader(stream))
                    {
                        do
                        {
                            reader.Read();
                            while (reader.Read())
                            {
                                var sf = reader.GetString(0);
                                var of = reader.GetString(1);
                                var skey = reader.GetString(2);
                                System.Console.WriteLine($"read:{sf}");

                                result.Add((sf, skey));
                            }
                        } while (reader.NextResult());
                    }
                }
                return result;
            }
            catch
            {
                return null;
            }
        }
    }

}